package org.hismig.cfg;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.hismig.cfg.ExtExecutor.Type;
import org.hismig.utils.CheckTool;
import org.hismig.utils.EncryptTool;
import org.hismig.utils.FileTool;
import org.hismig.utils.StringTool;

/**
 * @declare: //TODO <br />
 *           This class created by Vansion.Hopihit on 2012-07-04 <br />
 *           <a href="mailto:vansionhopihit@gmail.com">Contact Me</a>
 * @version 1.0
 * @author Vansion.Hopihit
 * 
 */
public class Configuration {

	private Document doc;
	private URI cfgUri;

	private Trigger trigger;
	private Strategy strategy;

	private List<DataBase> databases;

	public Configuration(String fileName) {
		// this(new File(new File(".").getAbsolutePath(), fileName).toURI());
		this(FileTool.getRuntimeFile(fileName).toURI());
	}

	public Configuration(URI uri) {
		cfgUri = uri;

		try {
			doc = new SAXReader().read(uri.toURL());
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException(String.format(
					"Can't read the config file : %1s", uri.toString()), e);
		} catch (DocumentException e) {
			throw new IllegalArgumentException(String.format(
					"Can't read the config file : %1s", uri.toString()), e);
		}

		setupConfig();
	}

	public List<DataBase> getDatabases() {
		return databases;
	}

	public Strategy getStrategy() {
		return strategy;
	}

	public Trigger getTrigger() {
		return trigger;
	}

	public void setDatabases(List<DataBase> databases) {
		this.databases = databases;
	}

	public void setStrategy(Strategy strategy) {
		this.strategy = strategy;
	}

	public void setTrigger(Trigger trigger) {
		this.trigger = trigger;
	}

	private void setupConfig() {
		setTrigger(setupTrigger(doc));
		setStrategy(setupStrategy(doc));
		setDatabases(setupDatabases(doc));
	}

	@SuppressWarnings("unchecked")
	private List<DataBase> setupDatabases(Document doc) {
		List<DataBase> databases = new ArrayList<DataBase>();

		List<Node> nodes_database = doc.selectNodes("/HisMig/Database");

		// must to have database cfg
		CheckTool.checkNotNull(nodes_database);
		CheckTool.checkArgument(!nodes_database.isEmpty());

		for (Node node_database : nodes_database) {

			DataBase database = new DataBase();

			database.setName(StringTool.trimBlank(node_database
					.valueOf("@name")));
			database.setSourceDBData(setupDBData(
					node_database.selectSingleNode("./SourceDBData"), database));
			database.setDistDBData(setupDBData(
					node_database.selectSingleNode("./DistDBData"), database));

			database.setSourcePreExtExecutor(setupExtExecutor(node_database
					.selectSingleNode("./PreExecutor[@to='source']"), database));
			database.setSourceAfterExtExecutor(setupExtExecutor(node_database
					.selectSingleNode("./AfterExecutor[@to='source']"),
					database));
			database.setDistPreExtExecutor(setupExtExecutor(
					node_database.selectSingleNode("./PreExecutor[@to='dist']"),
					database));
			database.setDistAfterExtExecutor(setupExtExecutor(node_database
					.selectSingleNode("./AfterExecutor[@to='dist']"), database));

			database.setParamBuilders(setupParamBuilders(
					node_database.selectNodes("./ParamBuilder"), database));
			database.setTables(setupTables(
					node_database.selectNodes("./Table"), database));

			Validator.validate(database);

			databases.add(database);
		}

		return databases;
	}

	private DBConnection setupDBConnection(Node node, DBData parent) {
		if (null != node) {
			// CheckTool.checkNotNull(node);
			DBConnection dbConnection = new DBConnection();

			String str_encrypt = StringTool.trimBlank(node.valueOf("@encrypt"));
			// CheckTool.checkNotEmpty(str_encrypt);

			dbConnection.setEncrypt(StringTool.convertToBoolean(str_encrypt,
					false));

			dbConnection.setParams(setupParams(node, dbConnection));

			dbConnection.setParent(parent);
			return dbConnection;
		}
		return null;
	}

	private DBData setupDBData(Node node, DataBase parent) {
		CheckTool.checkNotNull(node);

		DBData dbData = new DBData();

		dbData.setDbConnection(setupDBConnection(
				node.selectSingleNode("./DBConnection"), dbData));
		dbData.setDynamicPartition(setupDynamicPartition(
				node.selectSingleNode("./DynamicPartition"), dbData));

		dbData.setParent(parent);
		return dbData;
	}

	private DynamicPartition setupDynamicPartition(Node node, DBData parent) {
		if (null != node) {
			DynamicPartition dynamicPartition = new DynamicPartition();
			String className = node.valueOf("@class");
			CheckTool.checkNotEmpty(className);
			dynamicPartition.setClassName(className);

			String str_encrypt = StringTool.trimBlank(node.valueOf("@encrypt"));
			// CheckTool.checkNotEmpty(str_encrypt);

			dynamicPartition.setEncrypt(StringTool.convertToBoolean(
					str_encrypt, false));

			dynamicPartition.setParams(setupParams(node, dynamicPartition));

			dynamicPartition.setParent(parent);

			return dynamicPartition;
		}

		return null;
	}

	private ExtExecutor setupExtExecutor(Node node, Object parent) {
		if (null == node) {
			return null;
		}

		ExtExecutor extExecutor = new ExtExecutor();

		String className = StringTool.trimBlank(node.valueOf("@class"));

		extExecutor.setClassName(className);

		String to = StringTool.trimBlank(node.valueOf("@to"));

		extExecutor.setTo(to);

		if (node.getName().startsWith("Pre")) {
			extExecutor.setType(Type.PRE);
		} else if (node.getName().startsWith("After")) {
			extExecutor.setType(Type.AFTER);
		}

		extExecutor.setParams(setupParams(node, extExecutor));

		extExecutor.setParent(parent);
		return extExecutor;
	}

	private List<ParamBuilder> setupParamBuilders(List<Node> nodes,
			Object parent) {
		List<ParamBuilder> paramBuilders = new ArrayList<ParamBuilder>();

		if (null != nodes) {
			for (Node node_paramBulder : nodes) {

				ParamBuilder paramBuilder = new ParamBuilder();

				String className = StringTool.trimBlank(node_paramBulder
						.valueOf("@class"));
				paramBuilder.setClassName(className);

				String forName = StringTool.trimBlank(node_paramBulder
						.valueOf("@for"));
				paramBuilder.setForName(forName);

				paramBuilder.setParams(setupParams(node_paramBulder,
						paramBuilder));

				paramBuilder.setParent(parent);
				paramBuilders.add(paramBuilder);
			}
		}

		return paramBuilders;
	}

	@SuppressWarnings("unchecked")
	private List<Param> setupParams(Node node, Object parent) {
		List<Param> params = new ArrayList<Param>();
		List<Node> nodes_param = node.selectNodes("./Param");
		if (null != nodes_param) {
			for (Node node_param : nodes_param) {
				Param param = new Param();
				param.setName(StringTool.trimBlank(node_param.valueOf("@name")));
				param.setValue(StringTool.trimBlank(node_param.getText()));

				param.setParent(parent);

				params.add(param);
			}
		}

		return params;
	}

	private SelectCondition setupSelectCondition(Node node, Table parent) {
		if (null == node) {
			return null;
		}

		SelectCondition selectCondition = new SelectCondition();
		String value = StringTool.trimBlank(node.getText());
		selectCondition.setValue(value);

		selectCondition.setParent(parent);
		return selectCondition;
	}

	private Strategy setupStrategy(Document doc) {
		Node node_strategy = doc.selectSingleNode("/HisMig/Strategy");

		// must to have a strategy cfg
		CheckTool.checkNotNull(node_strategy);

		Strategy strategy = new Strategy();

		String className = StringTool
				.trimBlank(node_strategy.valueOf("@class"));

		strategy.setClassName(className);
		strategy.setParams(setupParams(node_strategy, strategy));

		Validator.validate(strategy);

		return strategy;
	}

	@SuppressWarnings("unchecked")
	private List<Table> setupTables(List<Node> nodes, DataBase parent) {
		List<Table> tables = new ArrayList<Table>();

		if (null != nodes) {
			for (Node node_table : nodes) {
				Table table = new Table();

				String name = StringTool.trimBlank(node_table.valueOf("@name"));
				table.setName(name);

				table.setSourcePreExtExecutor(setupExtExecutor(node_table
						.selectSingleNode("./PreExecutor[@to='source']"), table));
				table.setSourceAfterExtExecutor(setupExtExecutor(node_table
						.selectSingleNode("./AfterExecutor[@to='source']"),
						table));
				table.setDistPreExtExecutor(setupExtExecutor(node_table
						.selectSingleNode("./PreExecutor[@to='dist']"), table));
				table.setDistAfterExtExecutor(setupExtExecutor(node_table
						.selectSingleNode("./AfterExecutor[@to='dist']"), table));
				table.setParamBuilders(setupParamBuilders(
						node_table.selectNodes("./ParamBuilder"), table));
				table.setSelectCondition(setupSelectCondition(
						node_table.selectSingleNode("./SelectCondition"), table));

				table.setParent(parent);

				tables.add(table);
			}
		}

		return tables;
	}

	private Trigger setupTrigger(Document doc) {

		Node node_trigger = doc.selectSingleNode("/HisMig/Trigger");

		// must to have a tirrger cfg
		CheckTool.checkNotNull(node_trigger);
		Trigger trigger = new Trigger();

		String className = StringTool.trimBlank(node_trigger.valueOf("@class"));

		trigger.setClassName(className);
		trigger.setParams(setupParams(node_trigger, trigger));

		Validator.validate(trigger);

		return trigger;
	}

	public void updateConfig() {
		setupConfig();
	}

	private void saveOrUpdateXmlFile() {
		XMLWriter writer = null;
		try {
			OutputFormat output = OutputFormat.createPrettyPrint();
			output.setEncoding("UTF-8");
			output.setLineSeparator(System.getProperty("line.separator"));

			writer = new XMLWriter(new FileWriter(new File(cfgUri)), output);

			writer.write(doc);
		} catch (IOException e) {
			throw new RuntimeException(String.format(
					"Can't save the XML file : %s", cfgUri.getPath()), e);
		} finally {
			try {
				if (null != writer) {
					writer.close();
					writer = null;
				}
			} catch (IOException e) {
				throw new RuntimeException(String.format(
						"Can't close writer when save the XML file : %s",
						cfgUri.getPath()), e);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void encryptCfg() {

		List<Node> nodes = doc.selectNodes("//*[@encrypt='false']");

		if (null != nodes && !nodes.isEmpty()) {

			for (Node node : nodes) {
				Node node_user = node.selectSingleNode("./Param[@name='user']");
				if (null != node_user) {
					node_user.setText(EncryptTool.encrypt(node_user.getText()));
				}

				Node node_password = node
						.selectSingleNode("./Param[@name='password']");

				if (null != node_password) {
					node_password.setText(EncryptTool.encrypt(node_password
							.getText()));
				}

				Element e = (Element) node;
				e.attribute("encrypt").setValue("true");
			}
			saveOrUpdateXmlFile();
		}

	}

	@SuppressWarnings("unchecked")
	public void decryptCfg() {

		List<Node> nodes = doc.selectNodes("//*[@encrypt='true']");

		if (null != nodes && !nodes.isEmpty()) {
			for (Node node : nodes) {
				Node node_user = node.selectSingleNode("./Param[@name='user']");
				if (null != node_user) {
					node_user.setText(EncryptTool.decrypt(node_user.getText()));
				}

				Node node_password = node
						.selectSingleNode("./Param[@name='password']");

				if (null != node_password) {
					node_password.setText(EncryptTool.decrypt(node_password
							.getText()));
				}

				Element e = (Element) node;
				e.attribute("encrypt").setValue("false");
			}
			saveOrUpdateXmlFile();
		}

	}

}
